#define _CRT_SECURE_NO_WARNINGS

#include <windows.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include "libqlyrics.h"

#define MAX_PIPE_BUFFER_SIZE 4096

static Callback eventCallback[RequestEnd];
static HANDLE pipeHandle = NULL;
static FILE* logFile = NULL;


void writeLog(const char* format, ...) {
    /*char buffer[MAX_PIPE_BUFFER_SIZE];
    int length = 0;
    va_list args;
    va_start(args, format);
    length = vsprintf(buffer, format, args);
    va_end(args);
    if (length > 0) {
        time_t t = time(NULL);
        char* tm = ctime(&t);
        tm[strlen(tm) - 2] = '\0';
        if (!logFile)
            logFile = fopen("libqlyrics.log", "a");
        fprintf(logFile, "[%s] %s", tm, buffer);
        fflush(logFile);
        //fclose(logFile);
    };*/
};

void writeResponse(const char* data, int length) {
    BOOLEAN isSuccess = FALSE;
    DWORD bytesWritten = 0;

    if (!pipeHandle)
        return;

    isSuccess = WriteFile(
        pipeHandle,         // handle to pipe
        data,               // buffer to write from
        length,             // number of bytes to write
        &bytesWritten,      // number of bytes written
        NULL);              // not overlapped I/O

    FlushFileBuffers(pipeHandle);

    writeLog("--- Write response: %s", data);

    if (!isSuccess || length != bytesWritten) {
        writeLog("!!! Failed to write to the pipe\n");
    };
};

void printResponse(const char* format, ...) {
    char buffer[MAX_PIPE_BUFFER_SIZE];
    int length = 0;
    va_list args;
    va_start(args, format);
    length = vsprintf(buffer, format, args);
    va_end(args);
    if (length > 0)
        writeResponse(buffer, length);
};

static void cleanup() {
    if (pipeHandle) {
        CloseHandle(pipeHandle);
        pipeHandle = NULL;
    };
};

static void parseRequest(const char* requestString) {
    int event;  // should be RequestEvent type
    sscanf(requestString, "%d", &event);
    writeLog("--- Got event, id = %d\n", event);
    if (event < (int)RequestEnd && eventCallback[event] != NULL) {
        printResponse("%d %s\n", event, eventCallback[event]());
    };
};

static DWORD WINAPI eventMonitor(LPVOID parameter) {
    BOOLEAN isSuccess = FALSE;
    int requestSize = 0, size = 0;
    int pos = 0;
    char requestBuffer[MAX_PIPE_BUFFER_SIZE];
    char requestString[MAX_PIPE_BUFFER_SIZE];

    while (1) {
        isSuccess = PeekNamedPipe(
            pipeHandle,     // pipe handle
            NULL,           // buffer addr
            0,              // buffer size
            NULL,           // bytes read
            &size,          // total bytes
            NULL);          // bytes left
        
        if (isSuccess && size == 0) {
            Sleep(50);
            continue;
        };

        // Read client requests from the pipe.
        isSuccess = ReadFile(
            pipeHandle,                             // handle to pipe
            requestBuffer + requestSize,            // buffer to receive data
            sizeof(requestBuffer) - requestSize,    // size of buffer
            &size,                                  // number of bytes read
            NULL);                                  // not overlapped I/O

        if (!isSuccess || size == 0) {
            if (!isSuccess) {
                writeLog("!!! Failed to read from pipe, error = %d, monitor exited.\n", GetLastError());
            } else {
                writeLog("!!! Got zero-size request, monitor exited.\n");
            };
            cleanup();
            return 0;
        };

        requestSize += size;
        
        // Find lines in the buffer
        for (pos = 0; pos < requestSize; pos++) {
            if (requestBuffer[pos] == '\n') {
                memcpy(requestString, requestBuffer, pos);
                requestString[pos + 1] = '\0';
                parseRequest(requestString);
                memmove(&requestBuffer[pos + 1], requestBuffer, requestSize - pos - 1);
                requestSize -= pos + 1;
                pos = -1;
            };
        };
    };
    return 1;
};

void lrcRegisterEvent(const RequestEvent event, Callback func) {
    eventCallback[event] = func;
};

int lrcIsRunning() {
    return pipeHandle == NULL;
};

void lrcStart() {
    LPTSTR pipePattern = TEXT("\\\\.\\pipe\\qlyrics%d");
    LPTSTR configPattern = TEXT("%s\\qlyrics\\config.ini");
    LPTSTR paramPattern = TEXT("--plugin qlyrics%d");
    int pipeId = 0, size = 0;
    BOOLEAN isConnected = 0;
    HINSTANCE clientInstance = 0;
    HANDLE threadHandle = NULL;
    DWORD threadId = 0;
    OVERLAPPED overlap;
    HANDLE overlapEvent;
    WCHAR configFile[MAX_PATH];
    WCHAR pipeName[MAX_PATH];
    WCHAR exePath[MAX_PATH];
    WCHAR param[MAX_PATH];

    writeLog("--- Starting lyrics client\n");

    srand((int)time(NULL));
    pipeId = rand() % 100;
    wsprintf(pipeName, pipePattern, pipeId);

    //fwprintf(logFile, TEXT("--- Pipe name: %s\n"), pipeName);

    wsprintf(param, paramPattern, pipeId);

    pipeHandle = CreateNamedPipe(
        pipeName,                   // pipe name
        PIPE_ACCESS_DUPLEX |        // read/write access
        FILE_FLAG_OVERLAPPED,       // overlapped mode 
        PIPE_TYPE_MESSAGE |         // message type pipe
        PIPE_READMODE_MESSAGE |     // message-read mode
        PIPE_WAIT,                  // blocking mode
        1,                          // max. instances
        MAX_PIPE_BUFFER_SIZE,       // output buffer size
        MAX_PIPE_BUFFER_SIZE,       // input buffer size
        0,                          // client time-out
        NULL);                      // default security attribute

    if (pipeHandle == INVALID_HANDLE_VALUE) {
        writeLog("!!! Failed to create pipe.\n");
        return;
    };

    // Find the path of the executable
    wsprintf(configFile, configPattern, _wgetenv(TEXT("USERPROFILE")));
    GetPrivateProfileString(
        TEXT("System"),
        TEXT("Path"),
        TEXT("qlyrics"),
        exePath, sizeof(exePath),
        configFile);

    //fwprintf(logFile, TEXT("--- Client path: %s\n"), exePath);

    clientInstance = ShellExecute(
        NULL,               // owner window hwnd
        TEXT("open"),       // operation
        exePath,            // file
        param,              // parameter
        NULL,               // directory
        SW_SHOW);           // show cmd

    if ((int)clientInstance < 32) {
        writeLog("!!! Failed to execute lyrics client.\n");
        cleanup();
        return;
    };

    writeLog("--- Waiting client to connect.\n");

    // Wait for the client to connect
    overlapEvent = CreateEvent( 
         NULL,      // default security attribute 
         TRUE,      // manual-reset event 
         TRUE,      // initial state = signaled 
         NULL);     // unnamed event object 
    if (overlapEvent == NULL) {
        writeLog("!!! Failed to wait for client's connection.\n");
        cleanup();
        return;
    };
    memset(&overlap, 0, sizeof(overlap));
    overlap.hEvent = overlapEvent;
    isConnected = ConnectNamedPipe(pipeHandle, &overlap);
    // Overlapped ConnectNamedPipe should return zero. 
    if (!isConnected && GetLastError() == ERROR_PIPE_CONNECTED) {
        isConnected = SetEvent(overlapEvent) ? TRUE : FALSE;
    } else if (!isConnected && GetLastError() == ERROR_IO_PENDING) {
        // Wait for 10s
        WaitForSingleObject(overlapEvent, 10000);
        isConnected = GetOverlappedResult(
            pipeHandle,     // file handle
            &overlap,       // overlap struct
            &size,          // bytes transferred
            FALSE);         // no wait
    } else {
        isConnected = FALSE;
    };

    if (!isConnected) {
        writeLog("!!! Client failed to connect to the pipe.\n");
        cleanup();
        return;
    };
    writeLog("--- Client connected.\n");

    // Create Monitor Thread
    threadHandle = CreateThread(
        NULL,               // no security attribute
        0,                  // default stack size
        eventMonitor,       // thread proc
        NULL,               // thread parameter
        0,                  // not suspended
        &threadId);         // returns thread ID

    if (threadHandle == NULL) {
        writeLog("!!! Failed to connect monitor thread.\n");
        cleanup();
        return;
    } else {
        CloseHandle(threadHandle);
    };
};


void lrcStop() {
    if (pipeHandle == NULL)
        return;
    printResponse("%d\n", ResponseExit);
    DisconnectNamedPipe(pipeHandle);
    CloseHandle(pipeHandle);
    pipeHandle = NULL;
    writeLog("--- Stop lyrics client\n");
};

void lrcSetStatus(Status status) {
    printResponse("%d %d\n", ResponseStatusChange, status);
};

void lrcChangeTrack() {
    printResponse("%d\n", ResponseTrackChange);
};

void lrcConfigure() {
    printResponse("%d\n", ResponseConfigure);
};

void lrcSetPlaybackTime(int currentTime) {
    printResponse("%d %d\n", ResponseTime, currentTime);
};

